﻿function MinesWeeper(selector, options) {

    var $ = initTools();
    var defaults = {
        col: 15,
        row: 15,
        mine: 30,
        win: function() { alert("you win!"); },
        lose: function() { alert("you lose!"); },
        beforeTread: function(){},
        afterTread: function(){}
    };
    var conf = $.extend({}, defaults, options);

    function init(options) {

        $.extend(conf, options);

        var map = [];
        var mines = [];
        var remanent = conf.row * conf.col;  //地图上剩余的格子，如果剩余格子等于地雷数，说明已扫完所有雷
        var status = 0;

        var VOID = 0;
        var MINE = -1;

        createMap();
        createMines();
        createSigns();
        createHtml();
        createEvents();

        function createMap() {
            for(var i=0; i<conf.row; i++){
                var cols = [];
                for (var j=0; j<conf.col; j++){
                    cols.push({
                        value: null,
                        isTread: false,
                        isMarked: false,
                        isCleared: false
                    });
                }
                map.push(cols);
            }
        }

        function createMines() {
            var i = 0;
            while (i < conf.mine){
                var mine = { row: RandomNumber(0, conf.row-1), col: RandomNumber(0, conf.col-1) };
                if (!checkMineRepeat(mine)){
                    map[mine.row][mine.col].value = MINE;
                    mines.push(mine);
                    i++;
                }
            }
        }

        function getMinesFromMap() {
            var mines = [];
            map.forEach(function (row) {
                row.forEach(function (cell) {
                    if (cell.value === MINE) mines.push(cell);
                });
            });
            return mines;
        }

        function checkMineRepeat(mine) {
            for (var i=0; i<mines.length; i++){
                var m1 = mines[i];
                if (mine.row === m1.row && mine.col === m1.col){
                    return true;
                }
            }
            return false;
        }
        
        // 创建地雷旁边的数字标记
        function createSigns() {
            for (var i=0; i<conf.row; i++){
                for (var j=0; j<conf.col; j++){
                    var point = map[i][j];
                    if (point.value !== MINE){
                        var count = VOID;
                        getGrid(i, j).forEach(function (point) {
                            if (map[point.row][point.col].value === MINE){
                                count++;
                            }
                        });
                        if (point.value !== MINE) point.value = count;
                    }
                }
            }
        }
        
        // 获取地图中以给定坐标为中心3x3矩阵的数据
        function getGrid(row, col) {
            var grid = [];
            for (var i=row-1; i<=row+1; i++){
                for (var j=col-1; j<=col+1; j++){
                    if (i === row && j === col) continue;
                    if (i > -1 && i < conf.row && j > -1 && j < conf.col){
                        grid.push({ row: i, col: j });
                    }
                }
            }
            return grid;
        }

        // 取随机数
        function RandomNumber(min, max) {
            return Math.floor(Math.random() * max + min);
        }

        function createHtml() {
            var rows = "";
            for(var i=0; i<conf.row; i++){
                var cols = "";
                for(var j=0; j<conf.col; j++){
                    var point = map[i][j],
                        className = "",
                        classList = [],
                        text = "";
                    if (point.isTread){
                        classList.push("tread");
                        if (point.value > 0){
                            text = point.value;
                            classList.push("level" + point.value);
                        }
                        if (point.value === MINE){
                            classList.push("bomb");
                        }
                    }
                    if (point.isMarked){
                        classList.push("marked");
                    }
                    if (point.isCleared){
                        classList.push("clear");
                    }
                    className = classList.join(" ");
                    cols += '<td data-col="'+j+'" data-row="'+i+'" class="'+className+'">'+text+'</td>';
                }
                rows += '<tr>'+cols+'</tr>';
            }
            $(selector).html('<table cellspacing="0">'+rows+'</table>');
        }

        function createEvents() {
        	
            // 禁止鼠标默认右键菜单
            document.body.oncontextmenu = function (e) {
                window.event.returnValue = false;
                e.preventDefault();
                return false;
            };
            
            // 监听鼠标左右键
            $(selector).on("mousedown", "td", function (e) {

                var row = $(this).attr("data-row").toInt();
                var col = $(this).attr("data-col").toInt();

                conf.beforeTread.call(this);

                // 踩过的格子
                if (map[row][col].isTread) return;

                if (e.which === 1){
                    // 鼠标左键
                    treads(row, col);
                } else if (e.which === 3){
                    // 鼠标右键
                    mark(row, col);
                }

                conf.afterTread.call(this);
            });
        }

        function treads(row, col) {
            tread(row, col);
            if (map[row][col].value === 0){
                twoWayForeach(row, 0, conf.row, function (i) {
                    twoWayForeach(col, 0, conf.col, function (j) {
                        return treadsProcess(i, j);
                    });
                    return treadsProcess(i, col);
                });
                twoWayForeach(col, 0, conf.col, function (j) {
                    twoWayForeach(row, 0, conf.row, function (i) {
                        return treadsProcess(i, j);
                    });
                    return treadsProcess(row, j);
                });
            }
        }

        function treadsProcess(row, col) {
            var point = map[row][col];
            if (point.value >= VOID && !point.isTread){
                tread(row, col);
            }
            if (point.value > VOID){
                return -2;
            }
        }

        function twoWayForeach(index, min, max, callback) {
            var returnValue = null;
            for (var i=index; i>=min; i--){
                returnValue = callback(i);
                if (returnValue === -1) continue;
                if (returnValue === -2) break;
            }
            for (var j=index; j<max; j++){
                returnValue = callback(j);
                if (returnValue === -1) continue;
                if (returnValue === -2) break;
            }
        }

        function tread(row, col) {
            var point = map[row][col];
            point.isMarked = false;
            point.isTread = true;
            createHtml();
            remanent--;

            if (status !== 0) return;
            if (remanent === mines.length){
                status = 1;
                win();
            } else if (point.value === MINE){
                status = -1;
                lose();
            }
        }

        function mark(row, col) {
            var point = map[row][col];
            point.isMarked = point.isMarked ? false : true;
            createHtml();
        }

        function win() {
            mines.forEach(function (mine) {
                var point = map[mine.row][mine.col];
                point.isMarked = false;
                point.isCleared = true;
                tread(mine.row, mine.col);
            });
            conf.win();
            gameover();
        }

        function lose() {
            mines.forEach(function (mine) {
                var point = map[mine.row][mine.col];
                if (point.isMarked){
                    point.isMarked = false;
                    point.isCleared = true;
                }
                tread(mine.row, mine.col);
            });
            conf.lose();
            gameover();
        }

        function gameover() {
            $(selector).off("mousedown");
        }
    }

    function initTools() {

        var EVENTS = [];

        function DOMTool(element) {
            
            var els = null;
            if (typeof element === "string"){
                els = document.querySelectorAll(element);
            } else {
                els = [element];
            }
            
            this.html = function (text) {
                els.forEach(function (el) {
                    el.innerHTML = text;
                });
                return this;
            };
            
            this.attr = function (attr, value) {
                if (arguments.length === 1 && typeof attr === "string"){
                    return els[0].getAttribute(attr);
                } else {
                    els.forEach(function (el) {
                        if (typeof attr === "string"){
                            el.setAttribute(attr, value);
                        } else {
                            for (var key in attr){
                                el.setAttribute(key, attr[key]);
                            }
                        }
                    });
                    return els;
                }
            };
            
            this.addClass = function (className) {
                els.forEach(function (el) {
                    el.classList.add(className);
                });
                return this;
            };
            
            this.removeClass = function (className) {
                els.forEach(function (el) {
                    el.classList.remove(className);
                });
                return this;
            };
            
            this.on = function (eventName, selector, handle, useCapture) {
                var IsDelegate = true;
                if (typeof selector === "function") {
                    IsDelegate = false;
                    handle = selector;
                }
                els.forEach(function (el) {
                    var delegate = IsDelegate === false ? handle : function (e) {
                        var children = el.querySelectorAll(selector);
                        for (var i=0; i<children.length; i++){
                            var child = children[i];
                            if (child === e.target) {
                                handle.call(child, e);
                                break;
                            }
                        }
                    };
                    el.addEventListener(eventName, delegate, useCapture);
                    EVENTS.push({
                        target: el,
                        event: eventName,
                        selector: selector,
                        handle: delegate
                    });
                });
                return this;
            };
            
            this.off = function (eventName, handle, useCapture) {
                els.forEach(function (el) {
                    if (!handle){
                        var evt = EVENTS.find(el, eventName);
                        if (evt) handle = evt.handle;
                    }
                    el.removeEventListener(eventName, handle, useCapture);
                });
            };
            
            return this;
        }

        DOMTool.extend = function (obj) {
            for (var i=1; i<arguments.length; i++){
                var ext = arguments[i];
                for (var key in ext){
                    if (ext[key]){
                        obj[key] = ext[key];
                    }
                }
            }
            return obj;
        };

        String.prototype.toInt = function () {
            return parseInt(this);
        };
		
		NodeList.prototype.forEach = Array.prototype.forEach;

        EVENTS.find = function (target, eventName) {
            for (var i=0; i<this.length; i++){
                var evt = EVENTS[i];
                if (evt.target === target && evt.event === eventName){
                    return evt;
                }
            }
        };
        
        return DOMTool;
    }
    
    this.init = init;
    
}
